/*
 * COPYRIGHT:	2010 Christopher Smith, licenced under the GNU GPL v2; see COPYING in the top level directory
 * PROJECT:		sdedit
 * FILE:		ntobjtypes.cpp
 * PURPOSE:		object manager types
 */

namespace Ntapi
{

using namespace ObjectTypes;

namespace Specific
{

PCWSTR File[DESC_SPECIFIC_SIZE] = 
{
	L"Read Data/List",
	L"Write Data/Add File",
	L"Append/Add Subdir",
	L"Read Ea",
	L"Write Ea",
	L"Execute/Traverse",
	L"Delete Child",
	L"Read Attributes",
	L"Write Attributes"
};

GENERIC_MAPPING FileGenericMapping = 
{
	FILE_READ_ATTRIBUTES | FILE_READ_DATA | FILE_READ_EA | STANDARD_RIGHTS_READ | SYNCHRONIZE,
	FILE_APPEND_DATA | FILE_WRITE_ATTRIBUTES | FILE_WRITE_DATA | FILE_WRITE_EA | STANDARD_RIGHTS_WRITE | SYNCHRONIZE,
	FILE_READ_ATTRIBUTES | FILE_EXECUTE | STANDARD_RIGHTS_EXECUTE | SYNCHRONIZE,
	FILE_ALL_ACCESS
};

PCWSTR Key[DESC_SPECIFIC_SIZE] = 
{
	L"Query Value",
	L"Set Value",
	L"Create Subkey",
	L"List Subkeys",
	L"Notify",
	L"Create Link"
};

GENERIC_MAPPING KeyGenericMapping = 
{
	KEY_READ,
	KEY_WRITE,
	KEY_EXECUTE,
	KEY_ALL_ACCESS
};

PCWSTR Token[DESC_SPECIFIC_SIZE] = 
{
	L"Assign as Primary",
	L"Duplicate",
	L"Impersonate",
	L"Query",
	L"Query Source",
	L"Adjust Privileges",
	L"Adjust Groups",
	L"Adjust Default",
	L"Adjust SessionID"
};

PCWSTR Process[DESC_SPECIFIC_SIZE] = 
{
	L"Terminate",
	L"Create Thread",
	L"Set SessionID",
	L"VM Operation",
	L"VM Read",
	L"VM Write",
	L"Dup Handle",
	L"Create Process",
	L"Set Quota",
	L"Set Info",
	L"Query Info",
	L"Suspend/Resume"
};

PCWSTR Thread[DESC_SPECIFIC_SIZE] = 
{
	L"Terminate",
	L"Suspend/Resume",
	L"Get Context",
	L"Set Context",
	L"Query Info",
	L"Set Token",
	L"Impersonate",
	L"Direct Impersonate"
};

PCWSTR Job[DESC_SPECIFIC_SIZE] = 
{
	L"Assign Process",
	L"Set Attributes",
	L"Query",
	L"Terminate",
	L"Set Security Attribs"
};

PCWSTR Directory[DESC_SPECIFIC_SIZE] = 
{
	L"Query",
	L"Traverse",
	L"Create Object",
	L"Create Subdir"
};

PCWSTR Section[DESC_SPECIFIC_SIZE] = 
{
	L"Query",
	L"Map Write",
	L"Map Read",
	L"Map Execute",
	L"Extend Size"
};

PCWSTR Query[DESC_SPECIFIC_SIZE] = //symboliclink
{
	L"Query"
};

PCWSTR QueryModify[DESC_SPECIFIC_SIZE] = //event, semaphore, timer, mutant, iocompletion
{
	L"Query",
	L"Modify"
};

PCWSTR MandatoryIntegrityLabel[DESC_SPECIFIC_SIZE] = //for SYSTEM_MANDATORY_LABEL_ACE_TYPE
{
	L"Deny Write to Lower Integrity",
	L"Deny Read to Lower Integrity",
	L"Deny Execute to Lower Integrity"
};

}//namespace Specific

void SplitBackslashPath(auto_array<auto_szpod<WCHAR> > &Dest, PCWSTR SrcPath)
{
	if(!SrcPath)
		return;

	auto_szpod<WCHAR> Path = SrcPath;
	PWSTR Pos = Path, Start = Path;
	
	while(*Pos)
	{
		if(*Pos == '\\')
		{
			*Pos = 0;
			if(Pos != Start) //if not just a backslash at the beginning
				*Dest.next() = Start;
			Start = Pos + 1;
		}
		Pos++;
	}

	if(Pos != Start) //if not just a backslash at the end
		*Dest.next() = Start;
}

auto_take<WCHAR> TrimLastBackslashPathItem(PCWSTR SrcPath)
{
	if(!SrcPath)
		return auto_take<WCHAR>(NULL);

	auto_szpod<WCHAR> Path = SrcPath;
	PWSTR Pos = Path, LastSep = Path;
	
	while(*Pos)
	{
		if(*Pos == '\\' && *(Pos + 1)) //if this is not the last backslash
			LastSep = Pos;
		Pos++;
	}

	if(LastSep == Pos)
		return auto_take<WCHAR>(NULL); //no parts left

	*(LastSep + 1) = 0;


	return Path.release();
}

class NtSecDescObject : virtual public Object
{
protected:
	ULONG ExtraSdFlags;

public:
	NtSecDescObject(ULONG ExtraSdFlags = 0) :
		ExtraSdFlags(ExtraSdFlags)
	{}

	//request maximum, return writable in WriteSi if possible
	virtual auto_take<SECURITY_DESCRIPTOR_RELATIVE> QuerySecDesc(OUT SECURITY_INFORMATION *ReadSi, OUT SECURITY_INFORMATION *WriteSi) = 0;
	
	//request specified information
	virtual auto_take<SECURITY_DESCRIPTOR_RELATIVE> QuerySecDesc(IN SECURITY_INFORMATION Si) = 0;

	virtual void SetSecDesc(IN SECURITY_DESCRIPTOR const *Sd, IN SECURITY_INFORMATION Si) = 0;
	
	//is this specific object a container?
	virtual BOOLEAN IsContainer()
	{
		return FALSE;
	}

	//does this object type support auto inheritance, even if this object isn't a container?
	virtual BOOLEAN AutoInhSupport()
	{
		return FALSE;
	}

	static ACCESS_MASK ReqSecInfoAccess(SECURITY_INFORMATION ReadSi, SECURITY_INFORMATION WriteSi)
	{
		ACCESS_MASK Mask = 0;

		if(ReadSi & (OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION | DACL_SECURITY_INFORMATION | LABEL_SECURITY_INFORMATION))
			Mask |= READ_CONTROL;

		if(ReadSi & SACL_SECURITY_INFORMATION)
			Mask |= ACCESS_SYSTEM_SECURITY;

		if(WriteSi & (OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION))
			Mask |= WRITE_OWNER;

		if(WriteSi & DACL_SECURITY_INFORMATION)
			Mask |= WRITE_DAC;

		if(WriteSi & SACL_SECURITY_INFORMATION)
			Mask |= ACCESS_SYSTEM_SECURITY;

		if(WriteSi & LABEL_SECURITY_INFORMATION)
			Mask |= WRITE_OWNER;

		return Mask;
	}

	ULONG QueryFlags()
	{
		return (IsContainer() ? SD_FLAG_CONTAINER : 0) | ExtraSdFlags;
	}

	virtual void QueryEditState(OUT SD_EDIT_STATE &Es) override
	{
		//assume everything if the function can't be more specific
		SECURITY_INFORMATION ReadSi = DACL_SECURITY_INFORMATION | SACL_SECURITY_INFORMATION | OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION | LABEL_SECURITY_INFORMATION;
		SECURITY_INFORMATION WriteSi = DACL_SECURITY_INFORMATION | SACL_SECURITY_INFORMATION | OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION | LABEL_SECURITY_INFORMATION;

		auto_szpod<SECURITY_DESCRIPTOR_RELATIVE> Sd(QuerySecDesc(&ReadSi, &WriteSi));

		SDConv::NtSdToEs(Sd, ReadSi, WriteSi, Es, QueryFlags());

		if(AutoInhSupport())
			SdMerge::SetFullSupportFlags(Es);
	}

	virtual void QueryEditState(IN SECURITY_INFORMATION Si, OUT SD_EDIT_STATE &Es) override
	{
		SECURITY_INFORMATION WriteSi = DACL_SECURITY_INFORMATION | SACL_SECURITY_INFORMATION | OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION | LABEL_SECURITY_INFORMATION;

		auto_szpod<SECURITY_DESCRIPTOR_RELATIVE> Sd(QuerySecDesc(Si));

		SDConv::NtSdToEs(Sd, Si, WriteSi, Es, QueryFlags());

		if(AutoInhSupport())
			SdMerge::SetFullSupportFlags(Es);
	}

	virtual void SetEditState(IN const SD_EDIT_STATE &Es) override
	{
		SECURITY_INFORMATION Si;
		auto_szpod<SECURITY_DESCRIPTOR_RELATIVE> Sd = SDConv::EsToNtSd(Es, Si);

		SetSecDesc(c_cast<SECURITY_DESCRIPTOR *>(Sd.get()), Si);
	}
};

class ObjMgrObject : public NtSecDescObject
{
protected:
	HANDLE Handle;
	ACCESS_MASK GrantedAccess;

public:
	ObjMgrObject(HANDLE Handle, ACCESS_MASK GrantedAccess, PCWSTR const *SpecificHeaders = NULL, ULONG ExtraSdFlags = 0) :
		Object(SpecificHeaders, GetObjectName(Handle)),
		NtSecDescObject(ExtraSdFlags),
		Handle(Handle),
		GrantedAccess(GrantedAccess)
	{
		SplitBackslashPath(PathComponents, Name);
	}

	ObjMgrObject(HANDLE Handle, PCWSTR const *SpecificHeaders = NULL, ULONG ExtraSdFlags = 0) :
		Object(SpecificHeaders, GetObjectName(Handle)),
		NtSecDescObject(ExtraSdFlags),
		Handle(Handle)
	{
		OBJECT_BASIC_INFORMATION Obi;

		NTSTATUS Status = NtQueryObject(Handle, ObjectBasicInformation, &Obi, sizeof Obi, NULL);

		RaiseOnNtError(Status);

		GrantedAccess = Obi.GrantedAccess;

		SplitBackslashPath(PathComponents, Name);
	}

	virtual auto_take<WCHAR> GetObjectName(HANDLE Handle)
	{
		ULONG NameLength = 0x1000; //header + data + a null terminator

		auto_szpod<UNICODE_STRING> Name(NULL);

		while(TRUE)
		{
			Name.realloc(NameLength);

			NTSTATUS Status = NtQueryObject(Handle, ObjectNameInformation, Name, NameLength - sizeof(WCHAR), &NameLength);

			if(!NT_ERROR(Status))
			{
				if(!Name->Buffer || !Name->Length)
					return auto_take<WCHAR>(NULL);

				Name->Buffer[Name->Length / sizeof(WCHAR) + 1] = L'\0'; //null terminate
				break;
			}

			NameLength += sizeof(WCHAR); //one for terminator

			if(Status != STATUS_BUFFER_OVERFLOW)
				RaiseNtError(Status);
		}

		auto_szpod<WCHAR> Ret = Name->Buffer;
		return Ret.release();
	}

protected:
	void RaiseObjectNtError(NTSTATUS Status, PCWSTR Operation)
	{
		OpText Ot(Operation, Name);

		RaiseNtError(Status, FALSE);

		Ot;
	}

	void RaiseObjectSdError(NTSTATUS Status, SECURITY_INFORMATION Si, PCWSTR Verb)
	{
		WCHAR Text[MAX_STRING_CHARS];

		Text[0] = NULL;

		wcscat_s(Text, Verb);
		wcscat_s(Text, L" a security descriptor (");

		BOOLEAN PrevText = FALSE;

		if(Si & OWNER_SECURITY_INFORMATION)
		{
			wcscat_s(Text, L"Owner");
			PrevText = TRUE;
		}

		if(Si & GROUP_SECURITY_INFORMATION)
		{
			if(PrevText)
				wcscat_s(Text, L" ");
			wcscat_s(Text, L"Group");
			PrevText = TRUE;
		}

		if(Si & DACL_SECURITY_INFORMATION)
		{
			if(PrevText)
				wcscat_s(Text, L" ");
			wcscat_s(Text, L"DACL");
			PrevText = TRUE;
		}

		if(Si & SACL_SECURITY_INFORMATION)
		{
			if(PrevText)
				wcscat_s(Text, L" ");
			wcscat_s(Text, L"SACL");
			PrevText = TRUE;
		}

		if(Si & LABEL_SECURITY_INFORMATION)
		{
			if(PrevText)
				wcscat_s(Text, L" ");
			wcscat_s(Text, L"Label");
			PrevText = TRUE;
		}

		wcscat_s(Text, L")");

		RaiseObjectNtError(Status, Text);
	}

public:
	virtual auto_take<SECURITY_DESCRIPTOR_RELATIVE> QuerySecDesc(IN SECURITY_INFORMATION Si) override
	{
		ULONG SecurityDescriptorLengthReq = 0x1000;		
		auto_szpod<SECURITY_DESCRIPTOR_RELATIVE> Ret;

		while(TRUE)
		{
			Ret.realloc(SecurityDescriptorLengthReq);

			NTSTATUS Status = NtQuerySecurityObject(Handle, Si, Ret, SecurityDescriptorLengthReq, &SecurityDescriptorLengthReq);

			if(!NT_ERROR(Status))
				break;

			if((Status != STATUS_BUFFER_OVERFLOW) && (Status != STATUS_BUFFER_TOO_SMALL))
			{
				RaiseObjectSdError(Status, Si, L"getting");
				SecurityDescriptorLengthReq = 0x1000; //a bogus value may be returned through there when not a buffer size error
			}
		}

		return Ret.release();
	}

	virtual auto_take<SECURITY_DESCRIPTOR_RELATIVE> QuerySecDesc(OUT SECURITY_INFORMATION *ReadSi, OUT SECURITY_INFORMATION *WriteSi) override
	{
		*ReadSi = 0;
		
		if(GrantedAccess & READ_CONTROL)
			*ReadSi |= DACL_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION | OWNER_SECURITY_INFORMATION | LABEL_SECURITY_INFORMATION;

		if(GrantedAccess & ACCESS_SYSTEM_SECURITY)
			*ReadSi |= SACL_SECURITY_INFORMATION;

		*WriteSi = 0;

		if(GrantedAccess & WRITE_OWNER)
			*WriteSi |= OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION | LABEL_SECURITY_INFORMATION;

		if(GrantedAccess & WRITE_DAC)
			*WriteSi |= DACL_SECURITY_INFORMATION;

		if(GrantedAccess & ACCESS_SYSTEM_SECURITY)
			*WriteSi |= SACL_SECURITY_INFORMATION;

		return QuerySecDesc(*ReadSi);
	}

	virtual void SetSecDesc(IN SECURITY_DESCRIPTOR const *Sd, IN SECURITY_INFORMATION Si) override
	{
		NTSTATUS Status = STATUS_RETRY;

		//if(Si & SACL_SECURITY_INFORMATION)
		//	Si |= LABEL_SECURITY_INFORMATION;

		while(TRUE) //exit from break on success or unwinding on aborted error, retry otherwise
		{
			Status = NtSetSecurityObject(Handle, Si, Sd);

			if(!NT_ERROR(Status))
				break;

			RaiseObjectSdError(Status, Si, L"setting");
		}
	}

	virtual Object *OpenParent(UCHAR Privileges) override
	{
		auto_szpod<WCHAR> ParentName = TrimLastBackslashPathItem(Name);

		OBJECT_ATTRIBUTES ParentOa;
		UNICODE_STRING ParentName_UCS;

		RtlInitUnicodeString(&ParentName_UCS, ParentName);
		RtlInitObjectAttributes(&ParentOa, NULL, &ParentName_UCS, 0, 0, 0);

		HANDLE ParentDirectory;

		NTSTATUS Status = STATUS_RETRY;

		while(NT_ERROR(Status))
		{
			Status = NtOpenDirectoryObject(&ParentDirectory, MAXIMUM_ALLOWED | StandardPrivilegedRights(Privileges), &ParentOa);

			if(NT_ERROR(Status))
				RaiseObjectNtError(Status, L"Opening");
			else
				return new ObjMgrObject(ParentDirectory, Specific::Directory);
		}
		
		return NULL;
	}

	virtual ~ObjMgrObject()
	{
		if(Handle && Handle != INVALID_HANDLE_VALUE)
			NtClose(Handle);
	}
};

//can't use MAXIMUM_ALLOWED because it can lock the file unnecessarily or fail because the file is locked
//instead, probe for best relevant access
//all access rights listed are unrestricted by share modes
//FILE_LIST_DIRECTORY should be added for directories for merging
static ACCESS_MASK const AntiLockAttemptOrder[] =
{
	READ_CONTROL | WRITE_DAC | WRITE_OWNER | FILE_READ_ATTRIBUTES,
	READ_CONTROL | WRITE_DAC | WRITE_OWNER,
	READ_CONTROL | WRITE_DAC | FILE_READ_ATTRIBUTES,
	READ_CONTROL | WRITE_DAC,
	READ_CONTROL | WRITE_OWNER | FILE_READ_ATTRIBUTES,
	READ_CONTROL | WRITE_OWNER,
	WRITE_DAC | WRITE_OWNER | FILE_READ_ATTRIBUTES,
	WRITE_DAC | WRITE_OWNER,
	READ_CONTROL | FILE_READ_ATTRIBUTES,
	READ_CONTROL,
	WRITE_DAC | FILE_READ_ATTRIBUTES,
	WRITE_DAC,
	WRITE_OWNER | FILE_READ_ATTRIBUTES,
	WRITE_OWNER,
	FILE_READ_ATTRIBUTES,
	0
};

NTSTATUS AntiLockNtOpenFile(HANDLE *Handle, OBJECT_ATTRIBUTES *Oa, IO_STATUS_BLOCK *IoSb, UCHAR Privileges, ULONG OpenOptions)
{
	NTSTATUS Status = STATUS_UNSUCCESSFUL;

	for(ULONG i = 0; i < RTL_NUMBER_OF(AntiLockAttemptOrder); i++)
	{
		Status = NtOpenFile(Handle, AntiLockAttemptOrder[i] | StoragePrivilegedSecurityRights(Privileges), Oa, IoSb, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, OpenOptions);

		if(Status == STATUS_SHARING_VIOLATION || Status == STATUS_ACCESS_DENIED)
			continue;

		if(Status == STATUS_INVALID_PARAMETER && AntiLockAttemptOrder[i] == 0)
			return STATUS_ACCESS_DENIED;

		{//if it's a directory, try to get FILE_LIST_DIRECTORY too
			FILE_BASIC_INFORMATION Fbi;
			
			if(NT_SUCCESS(NtQueryInformationFile(*Handle, IoSb, &Fbi, sizeof Fbi, FileBasicInformation)))
			{
				if(Fbi.FileAttributes & FILE_ATTRIBUTE_DIRECTORY)
				{
					HANDLE Handle2;
					NTSTATUS Status2 = Status2 = NtOpenFile(&Handle2, FILE_LIST_DIRECTORY | AntiLockAttemptOrder[i] | StandardPrivilegedRights(Privileges), Oa, IoSb, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, OpenOptions);

					if(NT_SUCCESS(Status2))
					{
						NtClose(*Handle);
						*Handle = Handle2;
						return Status2;
					}
				}
			}
		}

		return Status;
	}

	return Status;
}

class File : public ObjMgrObject, public ContainerObject
{
protected:
	BOOLEAN IsDirectory;

public:
	File(HANDLE Handle, ULONG ExtraSdFlags OPTIONAL = 0) :
		Object(Specific::File, GetObjectName(Handle)),
		ObjMgrObject(Handle, (PCWSTR*)NULL, ExtraSdFlags),
		ContainerObject(Specific::FileGenericMapping)
	{
		IO_STATUS_BLOCK IoSb;
		FILE_BASIC_INFORMATION Fbo;

		NTSTATUS Status = NtQueryInformationFile(Handle, &IoSb, &Fbo, sizeof Fbo, FileBasicInformation);

		if(!NT_ERROR(Status))
			IsDirectory = (Fbo.FileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0;
	}

	File(HANDLE Handle, BOOLEAN IsDirectory) :
		Object(Specific::File, GetObjectName(Handle)),
		ObjMgrObject(Handle),
		ContainerObject(Specific::FileGenericMapping),
		IsDirectory(IsDirectory)
	{}

	virtual BOOLEAN IsContainer() override
	{
		return IsDirectory;
	}

	virtual BOOLEAN AutoInhSupport() override
	{
		return TRUE;
	}

private:
	static void PushChildFile(HANDLE Handle, BOOLEAN IsDirectory, ChildrenWalkDest &Dest)
	{
		File Child(Handle, IsDirectory);
		Dest.Proc(Child);
	}

public:
	virtual void WalkChildren(ChildrenWalkDest &Dest, UCHAR Privileges) override
	{
		if(!IsDirectory) //no subfiles if not directory
			return;

		ULONG DirInfoBaseSize = DIRECTORY_QUERY_BUFFER_SIZE;
		auto_szpod<FILE_DIRECTORY_INFORMATION> DirInfoBase = auto_szpod_empty(DirInfoBaseSize);
		IO_STATUS_BLOCK IoSb;
		BOOLEAN RestartScan = TRUE;

		union
		{
			FILE_DIRECTORY_INFORMATION *DirInfo;
			ULONG_PTR DirInfoPos;
		};

		while(TRUE)
		{
			DirInfo = DirInfoBase;
			ResetEvent(RtlThreadSynchEvent());

			NTSTATUS Status = NtQueryDirectoryFile(Handle, RtlThreadSynchEvent(), NULL, NULL, &IoSb, DirInfo, DirInfoBaseSize, FileDirectoryInformation, FALSE, NULL, RestartScan);
			RestartScan = FALSE;

			if(Status == STATUS_PENDING)
			{
				WaitForSingleObject(RtlThreadSynchEvent(), INFINITE);
				Status = IoSb.Status;
			}

			if(NT_FAILURE(Status))
				switch(Status)
				{
				case STATUS_NO_MORE_FILES:
				case STATUS_NO_SUCH_FILE:
					return;

				case STATUS_BUFFER_OVERFLOW: //not enough room for even 1 entry
					DirInfoBaseSize <<= 1;//double buffer size
					DirInfoBase.realloc(DirInfoBaseSize); 
					continue;

				case 0xcccccccc: //weird bug in which the return value from querydirectory is... uninitialized?
					continue;

				default:
					RaiseObjectNtError(Status, L"enumerating a directory");
				}

			while(TRUE)
			{
				UNICODE_STRING ChildName;
				OBJECT_ATTRIBUTES ChildOa;
				HANDLE ChildHandle = NULL;

				ChildName.Buffer = DirInfo->FileName;
				ChildName.Length = ChildName.MaximumLength = static_cast<USHORT>(DirInfo->FileNameLength); //the fs can be trusted to not provide too-long names

				if(!IsBogusFilename(&ChildName) && !(DirInfo->FileAttributes & FILE_ATTRIBUTE_REPARSE_POINT))
				//if(!IsBogusFilename(&ChildName))
				{
					RtlZeroLengthedStruct(ChildOa);
					ChildOa.ObjectName = &ChildName;
					ChildOa.RootDirectory = Handle;

					ULONG FileOptions = (Privileges & (PRIV_BACKUP | PRIV_RESTORE) ? FILE_OPEN_FOR_BACKUP_INTENT : 0);

					CppCancelOneHandlerExistance CCOHE;
					try
					{
						Status = STATUS_RETRY;
						while(NT_ERROR(Status))
						{
							Status = AntiLockNtOpenFile(&ChildHandle, &ChildOa, &IoSb, Privileges, FileOptions);

							if(NT_ERROR(Status))
								RaiseObjectNtError(Status, L"Opening");
							else
								PushChildFile(ChildHandle, (DirInfo->FileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0, Dest);
						}
					}
					catch(CancelOneException)
					{}
				}

				if(DirInfo->NextEntryOffset)
					DirInfoPos += DirInfo->NextEntryOffset;
				else
					break;
			}
		}
	}

	virtual Object *OpenParent(UCHAR Privileges) override
	{
		auto_szpod<WCHAR> ParentName = TrimLastBackslashPathItem(Name);

		OBJECT_ATTRIBUTES ParentOa;
		UNICODE_STRING ParentName_UCS;
		IO_STATUS_BLOCK IoSb;
		ULONG FileOptions = (Privileges & (PRIV_BACKUP | PRIV_RESTORE) ? FILE_OPEN_FOR_BACKUP_INTENT : 0);

		RtlInitUnicodeString(&ParentName_UCS, ParentName);
		RtlInitObjectAttributes(&ParentOa, NULL, &ParentName_UCS, 0, 0, 0);

		HANDLE ParentDirectory;

		NTSTATUS Status = STATUS_RETRY;
		while(NT_ERROR(Status))
		{
			Status = AntiLockNtOpenFile(&ParentDirectory, &ParentOa, &IoSb, Privileges, FileOptions);

			if(NT_ERROR(Status))
				RaiseObjectNtError(Status, L"Opening");
			else
				return new File(ParentDirectory, (BOOLEAN)TRUE);
		}

		return NULL;
	}
};

//this function is necessary since you can't specify REG_OPTION_BACKUP_RESTORE for an open operation: this attempts to emulate the expected behavior.
NTSTATUS NtOpenKeyWithOptions(HANDLE *Handle, ACCESS_MASK RequestedAccess, OBJECT_ATTRIBUTES *Oa, ULONG Options)
{
	if(Options)
	{
		ULONG Disposition;

		Oa->Attributes |= OBJ_OPENIF;

		NTSTATUS Status = NtOpenKey(Handle, RequestedAccess, Oa);

		if(Status != STATUS_ACCESS_DENIED)
			return Status; //if access denied, let's try the options (for backup/restore mode) that can only be specified for create

		Status = NtCreateKey(Handle, RequestedAccess | DELETE, Oa, 0, NULL, Options, &Disposition);

		if(Disposition == REG_CREATED_NEW_KEY) //oops, didn't mean to create a new key
		{
			{
				OpText Ot(L"Deleting this key, accidentally created");
				RaiseOnNtError(NtDeleteKey(Handle)); //if this doesn't work, we've caused key pollution
				Ot;
			}
			NtClose(Handle);
			return STATUS_OBJECT_NAME_NOT_FOUND;
		}

		return Status;
	}
	else
		return NtOpenKey(Handle, RequestedAccess, Oa);
}

class Key : public ObjMgrObject, public ContainerObject
{
public:
	Key(HANDLE Handle, ULONG ExtraSdFlags = 0 OPTIONAL) :
		Object(Specific::Key, GetObjectName(Handle)),
		ObjMgrObject(Handle, Specific::Key, ExtraSdFlags | SD_FLAG_NO_SYNCHRONIZE | SD_FLAG_CONTAINERS_ONLY),
		ContainerObject(Specific::KeyGenericMapping)
	{}
	
	Key(HANDLE Handle, ACCESS_MASK GrantedAccess, ULONG ExtraSdFlags) :
		Object(Specific::Key, GetObjectName(Handle)),
		ObjMgrObject(Handle, GrantedAccess, Specific::Key, ExtraSdFlags | SD_FLAG_NO_SYNCHRONIZE | SD_FLAG_CONTAINERS_ONLY),
		ContainerObject(Specific::KeyGenericMapping)
	{}

	//static Key *ReturnNew(HANDLE Handle)
	//{
	//	return new Key(Handle);
	//}

	//static Key *ReturnNew(HANDLE Handle, ACCESS_MASK GrantedAccess)
	//{
	//	return new Key(Handle, GrantedAccess);
	//}

	virtual BOOLEAN IsContainer() override
	{
		return TRUE;
	}
	virtual BOOLEAN AutoInhSupport() override
	{
		return TRUE;
	}

private:
	static void PushChildKey(HANDLE Handle, ChildrenWalkDest &Dest)
	{
		Key Child(Handle);
		Dest.Proc(Child);
	}

public:
	virtual void WalkChildren(ChildrenWalkDest &Dest, UCHAR Privileges) override
	{
		ULONG BufferSize = DIRECTORY_QUERY_BUFFER_SIZE;
		ULONG RequiredBufferSize;
		auto_szpod<KEY_BASIC_INFORMATION> KeyInfo = auto_szpod_empty(BufferSize);
		ULONG Index = 0;

		while(TRUE)
		{
			NTSTATUS Status = NtEnumerateKey(Handle, Index, KeyBasicInformation, KeyInfo, BufferSize, &RequiredBufferSize);

			if(NT_FAILURE(Status))
				switch(Status)
				{
				case STATUS_NO_MORE_ENTRIES:
					return;

				case STATUS_BUFFER_OVERFLOW:
					BufferSize = RequiredBufferSize;
					KeyInfo.realloc(BufferSize);
					continue;

				default:
					RaiseObjectNtError(Status, L"enumerating a key");
				}

			UNICODE_STRING ChildName;
			OBJECT_ATTRIBUTES ChildOa;
			HANDLE ChildHandle;
			
			ChildName.Buffer = KeyInfo->Name;
			ChildName.Length = ChildName.MaximumLength = static_cast<USHORT>(KeyInfo->NameLength);

			RtlZeroLengthedStruct(ChildOa);
			ChildOa.ObjectName = &ChildName;
			ChildOa.RootDirectory = Handle;

			Status = NtOpenKeyWithOptions(&ChildHandle, MAXIMUM_ALLOWED | StoragePrivilegedRights(Privileges), &ChildOa, Privileges & (PRIV_BACKUP | PRIV_RESTORE) ? REG_OPTION_BACKUP_RESTORE : 0);

			RaiseOnNtError(Status);
			
			PushChildKey(ChildHandle, Dest);

			Index++;
		}
	}

	virtual Object *OpenParent(UCHAR Privileges) override
	{
		auto_szpod<WCHAR> ParentName = TrimLastBackslashPathItem(Name);

		OBJECT_ATTRIBUTES ParentOa;
		UNICODE_STRING ParentName_UCS;

		RtlInitUnicodeString(&ParentName_UCS, ParentName);
		RtlInitObjectAttributes(&ParentOa, NULL, &ParentName_UCS, 0, 0, 0);

		HANDLE ParentKey;

		NTSTATUS Status = STATUS_RETRY;
		while(NT_ERROR(Status))
		{
			Status = NtOpenKeyWithOptions(&ParentKey, MAXIMUM_ALLOWED | StoragePrivilegedRights(Privileges), &ParentOa, Privileges & (PRIV_BACKUP | PRIV_RESTORE) ? REG_OPTION_BACKUP_RESTORE : 0);

			if(NT_ERROR(Status))
				RaiseObjectNtError(Status, L"Opening");
			else
				return new Key(ParentKey);
		}

		return NULL;
	}
};

NTSTATUS OpenNtProcess(HANDLE *Handle, ACCESS_MASK DesiredAccess, OBJECT_ATTRIBUTES Oa, ULONG_PTR ProcessId)
{
	if(ProcessId)
	{
		Oa.ObjectName = NULL; //it's an error to specify both a name and a clientid

		CLIENT_ID Cid;
		Cid.UniqueProcess = reinterpret_cast<HANDLE>(ProcessId);
		Cid.UniqueThread = NULL;

		return NtOpenProcess(Handle, DesiredAccess, &Oa, &Cid);
	}
	else
		return NtOpenProcess(Handle, DesiredAccess, &Oa, NULL);
}

NTSTATUS OpenNtThread(HANDLE *Handle, ACCESS_MASK DesiredAccess, OBJECT_ATTRIBUTES Oa, ULONG_PTR ThreadId)
{
	if(ThreadId)
	{
		Oa.ObjectName = NULL; //it's an error to specify both a name and a clientid

		CLIENT_ID Cid;
		Cid.UniqueThread = reinterpret_cast<HANDLE>(ThreadId);
		Cid.UniqueProcess = NULL;

		return NtOpenThread(Handle, DesiredAccess, &Oa, &Cid);
	}
	else
		return NtOpenThread(Handle, DesiredAccess, &Oa, NULL);
}

NTSTATUS OpenProcessForToken(HANDLE *TokenHandle, ACCESS_MASK DesiredAccess, OBJECT_ATTRIBUTES Oa, ULONG_PTR ProcessId)
{
	HANDLE ProcessHandle;

	NTSTATUS Status = OpenNtProcess(&ProcessHandle, PROCESS_QUERY_INFORMATION, Oa, ProcessId);

	if(NT_ERROR(Status))
		return Status;

	Status = NtOpenProcessToken(ProcessHandle, MAXIMUM_ALLOWED, TokenHandle);

	NtClose(ProcessHandle);

	return Status;
}

DelayedImportFunction<NTSTATUS (NTAPI *)(HANDLE *, ACCESS_MASK, OBJECT_ATTRIBUTES *)> NtOpenJobObject_Delayed(L"ntdll", "NtOpenJobObject");

}//namespace Ntapi